ExternalCustomEditorGUI.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. using System;
  2. using System.Collections;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEditor;
  6. using UnityEditor.SceneManagement;
  7. using UnityEngine;
  8. namespace ExternPropertyAttributes.Editor
  9. {
  10. public static class ExternalCustomEditorGUI
  11. {
  12. public const float IndentLength = 15.0f;
  13. public const float HorizontalSpacing = 2.0f;
  14. private static GUIStyle _buttonStyle = new GUIStyle(GUI.skin.button) { richText = true };
  15. private delegate void PropertyFieldFunction(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren);
  16. public static void PropertyField(Rect rect, SerializedProperty property, bool includeChildren)
  17. {
  18. PropertyField_Implementation(rect, property, includeChildren, DrawPropertyField);
  19. }
  20. public static void PropertyField_Layout(SerializedProperty property, bool includeChildren)
  21. {
  22. Rect dummyRect = new Rect();
  23. PropertyField_Implementation(dummyRect, property, includeChildren, DrawPropertyField_Layout);
  24. }
  25. private static void DrawPropertyField(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren) {
  26. EditorGUI.PropertyField(rect, property, label, includeChildren);
  27. }
  28. private static void DrawPropertyField_Layout(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren)
  29. {
  30. var originalRichText = EditorStyles.label.richText;
  31. EditorStyles.label.richText = true;
  32. EditorGUILayout.PropertyField(property, label, includeChildren);
  33. EditorStyles.label.richText = originalRichText;
  34. }
  35. private static void PropertyField_Implementation(Rect rect, SerializedProperty property, bool includeChildren, PropertyFieldFunction propertyFieldFunction)
  36. {
  37. var originalRichText = EditorStyles.label.richText;
  38. EditorStyles.label.richText = true;
  39. SpecialCaseDrawerAttribute specialCaseAttribute = PropertyUtility.GetAttribute<SpecialCaseDrawerAttribute>(property);
  40. if (specialCaseAttribute != null)
  41. {
  42. specialCaseAttribute.GetDrawer().OnGUI(rect, property);
  43. }
  44. else
  45. {
  46. GUIContent label = PropertyUtility.GetLabel(property);
  47. bool anyDrawerAttribute = PropertyUtility.GetAttributes<DrawerAttribute>(property).Any();
  48. if (!anyDrawerAttribute)
  49. {
  50. // Drawer attributes check for visibility, enableability and validator themselves,
  51. // so if a property doesn't have a DrawerAttribute we need to check for these explicitly
  52. // Check if visible
  53. bool visible = PropertyUtility.IsVisible(property);
  54. if (!visible)
  55. {
  56. return;
  57. }
  58. // Validate
  59. ValidatorAttribute[] validatorAttributes = PropertyUtility.GetAttributes<ValidatorAttribute>(property);
  60. foreach (var validatorAttribute in validatorAttributes)
  61. {
  62. validatorAttribute.GetValidator().ValidateProperty(property);
  63. }
  64. // Check if enabled and draw
  65. EditorGUI.BeginChangeCheck();
  66. bool enabled = PropertyUtility.IsEnabled(property);
  67. using (new EditorGUI.DisabledScope(disabled: !enabled))
  68. {
  69. propertyFieldFunction.Invoke(rect, property, label, includeChildren);
  70. }
  71. // Call OnValueChanged callbacks
  72. if (EditorGUI.EndChangeCheck())
  73. {
  74. PropertyUtility.CallOnValueChangedCallbacks(property);
  75. }
  76. }
  77. else
  78. {
  79. // We don't need to check for enableIfAttribute
  80. propertyFieldFunction.Invoke(rect, property, label, includeChildren);
  81. }
  82. }
  83. EditorStyles.label.richText = originalRichText;
  84. }
  85. public static float GetIndentLength(Rect sourceRect)
  86. {
  87. Rect indentRect = EditorGUI.IndentedRect(sourceRect);
  88. float indentLength = indentRect.x - sourceRect.x;
  89. return indentLength;
  90. }
  91. public static void BeginBoxGroup_Layout(string label = "")
  92. {
  93. EditorGUILayout.BeginVertical(GUI.skin.box);
  94. if (!string.IsNullOrEmpty(label))
  95. {
  96. EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
  97. }
  98. }
  99. public static void EndBoxGroup_Layout()
  100. {
  101. EditorGUILayout.EndVertical();
  102. }
  103. /// <summary>
  104. /// Creates a dropdown
  105. /// </summary>
  106. /// <param name="rect">The rect the defines the position and size of the dropdown in the inspector</param>
  107. /// <param name="serializedObject">The serialized object that is being updated</param>
  108. /// <param name="target">The target object that contains the dropdown</param>
  109. /// <param name="dropdownField">The field of the target object that holds the currently selected dropdown value</param>
  110. /// <param name="label">The label of the dropdown</param>
  111. /// <param name="selectedValueIndex">The index of the value from the values array</param>
  112. /// <param name="values">The values of the dropdown</param>
  113. /// <param name="displayOptions">The display options for the values</param>
  114. public static void Dropdown(
  115. Rect rect, SerializedObject serializedObject, object target, FieldInfo dropdownField,
  116. string label, int selectedValueIndex, object[] values, string[] displayOptions)
  117. {
  118. EditorGUI.BeginChangeCheck();
  119. int newIndex = EditorGUI.Popup(rect, label, selectedValueIndex, displayOptions);
  120. if (EditorGUI.EndChangeCheck())
  121. {
  122. Undo.RecordObject(serializedObject.targetObject, "Dropdown");
  123. // Problem with structs, because they are value type.
  124. // The solution is to make boxing/unboxing but unfortunately I don't know the compile time type of the target object
  125. dropdownField.SetValue(target, values[newIndex]);
  126. }
  127. }
  128. public static void Button(UnityEngine.Object target, MethodInfo methodInfo)
  129. {
  130. bool visible = ButtonUtility.IsVisible(target, methodInfo);
  131. if (!visible)
  132. {
  133. return;
  134. }
  135. if (methodInfo.GetParameters().All(p => p.IsOptional))
  136. {
  137. ButtonAttribute buttonAttribute = (ButtonAttribute)methodInfo.GetCustomAttributes(typeof(ButtonAttribute), true)[0];
  138. string buttonText = string.IsNullOrEmpty(buttonAttribute.Text) ? ObjectNames.NicifyVariableName(methodInfo.Name) : buttonAttribute.Text;
  139. bool buttonEnabled = ButtonUtility.IsEnabled(target, methodInfo);
  140. EButtonEnableMode mode = buttonAttribute.SelectedEnableMode;
  141. buttonEnabled &=
  142. mode == EButtonEnableMode.Always ||
  143. mode == EButtonEnableMode.Editor && !Application.isPlaying ||
  144. mode == EButtonEnableMode.Playmode && Application.isPlaying;
  145. bool methodIsCoroutine = methodInfo.ReturnType == typeof(IEnumerator);
  146. if (methodIsCoroutine)
  147. {
  148. buttonEnabled &= (Application.isPlaying ? true : false);
  149. }
  150. EditorGUI.BeginDisabledGroup(!buttonEnabled);
  151. if (GUILayout.Button(buttonText, _buttonStyle))
  152. {
  153. object[] defaultParams = methodInfo.GetParameters().Select(p => p.DefaultValue).ToArray();
  154. IEnumerator methodResult = methodInfo.Invoke(target, defaultParams) as IEnumerator;
  155. if (!Application.isPlaying)
  156. {
  157. // Set target object and scene dirty to serialize changes to disk
  158. EditorUtility.SetDirty(target);
  159. PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
  160. if (stage != null)
  161. {
  162. // Prefab mode
  163. EditorSceneManager.MarkSceneDirty(stage.scene);
  164. }
  165. else
  166. {
  167. // Normal scene
  168. EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
  169. }
  170. }
  171. else if (methodResult != null && target is MonoBehaviour behaviour)
  172. {
  173. behaviour.StartCoroutine(methodResult);
  174. }
  175. }
  176. EditorGUI.EndDisabledGroup();
  177. }
  178. else
  179. {
  180. string warning = typeof(ButtonAttribute).Name + " works only on methods with no parameters";
  181. HelpBox_Layout(warning, MessageType.Warning, context: target, logToConsole: true);
  182. }
  183. }
  184. public static void NativeProperty_Layout(UnityEngine.Object target, PropertyInfo property)
  185. {
  186. object value = property.GetValue(target, null);
  187. if (value == null)
  188. {
  189. string warning = string.Format("{0} is null. {1} doesn't support reference types with null value", property.Name, typeof(ShowNativePropertyAttribute).Name);
  190. HelpBox_Layout(warning, MessageType.Warning, context: target);
  191. }
  192. else if (!Field_Layout(value, property.Name))
  193. {
  194. string warning = string.Format("{0} doesn't support {1} types", typeof(ShowNativePropertyAttribute).Name, property.PropertyType.Name);
  195. HelpBox_Layout(warning, MessageType.Warning, context: target);
  196. }
  197. }
  198. public static void NonSerializedField_Layout(UnityEngine.Object target, FieldInfo field)
  199. {
  200. object value = field.GetValue(target);
  201. if (value == null)
  202. {
  203. string warning = string.Format("{0} is null. {1} doesn't support reference types with null value", field.Name, typeof(ShowNonSerializedFieldAttribute).Name);
  204. HelpBox_Layout(warning, MessageType.Warning, context: target);
  205. }
  206. else if (!Field_Layout(value, field.Name))
  207. {
  208. string warning = string.Format("{0} doesn't support {1} types", typeof(ShowNonSerializedFieldAttribute).Name, field.FieldType.Name);
  209. HelpBox_Layout(warning, MessageType.Warning, context: target);
  210. }
  211. }
  212. public static void HorizontalLine(Rect rect, float height, Color color)
  213. {
  214. rect.height = height;
  215. EditorGUI.DrawRect(rect, color);
  216. }
  217. public static void HelpBox(Rect rect, string message, MessageType type, UnityEngine.Object context = null, bool logToConsole = false)
  218. {
  219. EditorGUI.HelpBox(rect, message, type);
  220. if (logToConsole)
  221. {
  222. DebugLogMessage(message, type, context);
  223. }
  224. }
  225. public static void HelpBox_Layout(string message, MessageType type, UnityEngine.Object context = null, bool logToConsole = false)
  226. {
  227. EditorGUILayout.HelpBox(message, type);
  228. if (logToConsole)
  229. {
  230. DebugLogMessage(message, type, context);
  231. }
  232. }
  233. public static bool Field_Layout(object value, string label)
  234. {
  235. using (new EditorGUI.DisabledScope(disabled: true))
  236. {
  237. bool isDrawn = true;
  238. Type valueType = value.GetType();
  239. if (valueType == typeof(bool))
  240. {
  241. EditorGUILayout.Toggle(label, (bool)value);
  242. }
  243. else if (valueType == typeof(int))
  244. {
  245. EditorGUILayout.IntField(label, (int)value);
  246. }
  247. else if (valueType == typeof(long))
  248. {
  249. EditorGUILayout.LongField(label, (long)value);
  250. }
  251. else if (valueType == typeof(float))
  252. {
  253. EditorGUILayout.FloatField(label, (float)value);
  254. }
  255. else if (valueType == typeof(double))
  256. {
  257. EditorGUILayout.DoubleField(label, (double)value);
  258. }
  259. else if (valueType == typeof(string))
  260. {
  261. EditorGUILayout.TextField(label, (string)value);
  262. }
  263. else if (valueType == typeof(Vector2))
  264. {
  265. EditorGUILayout.Vector2Field(label, (Vector2)value);
  266. }
  267. else if (valueType == typeof(Vector3))
  268. {
  269. EditorGUILayout.Vector3Field(label, (Vector3)value);
  270. }
  271. else if (valueType == typeof(Vector4))
  272. {
  273. EditorGUILayout.Vector4Field(label, (Vector4)value);
  274. }
  275. else if (valueType == typeof(Color))
  276. {
  277. EditorGUILayout.ColorField(label, (Color)value);
  278. }
  279. else if (valueType == typeof(Bounds))
  280. {
  281. EditorGUILayout.BoundsField(label, (Bounds)value);
  282. }
  283. else if (valueType == typeof(Rect))
  284. {
  285. EditorGUILayout.RectField(label, (Rect)value);
  286. }
  287. else if (typeof(UnityEngine.Object).IsAssignableFrom(valueType))
  288. {
  289. EditorGUILayout.ObjectField(label, (UnityEngine.Object)value, valueType, true);
  290. }
  291. else if (valueType.BaseType == typeof(Enum))
  292. {
  293. EditorGUILayout.EnumPopup(label, (Enum)value);
  294. }
  295. else if (valueType.BaseType == typeof(System.Reflection.TypeInfo))
  296. {
  297. EditorGUILayout.TextField(label, value.ToString());
  298. }
  299. else
  300. {
  301. isDrawn = false;
  302. }
  303. return isDrawn;
  304. }
  305. }
  306. private static void DebugLogMessage(string message, MessageType type, UnityEngine.Object context)
  307. {
  308. switch (type)
  309. {
  310. case MessageType.None:
  311. case MessageType.Info:
  312. Debug.Log(message, context);
  313. break;
  314. case MessageType.Warning:
  315. Debug.LogWarning(message, context);
  316. break;
  317. case MessageType.Error:
  318. Debug.LogError(message, context);
  319. break;
  320. }
  321. }
  322. }
  323. }